import { VmTyp, CompleTyp } from './types'
import { getValue, setValue } from './utils'
import Watcher from './Watcher'
interface TransValueKeeper {
    key: string,
    outerKey: string,
    curVal: string
}
export default class Complier implements CompleTyp {
    el: HTMLElement
    vm: VmTyp
    constructor(vm: VmTyp) {
        this.el = vm.$el
        this.vm = vm
        this.complie(this.el)
    }
    complie(el: HTMLElement) {
        let childNodes = el.childNodes as NodeListOf<HTMLElement>;
        if (childNodes && childNodes.length) {
            const nodes = [...childNodes]
            nodes.forEach(node => {
                if (this.isTextNode(node)) {
                    this.complieTextNode(node)
                } else if (this.isElementNode(node)) {
                    this.complieElementNode(node)
                }

                if (node && node.childNodes) {
                    this.complie(node)
                }
            })
        }
    }
    isTextNode(node: HTMLElement) {
        return node.nodeType === 3
    }
    complieTextNode(node: HTMLElement) {
        let text = node.textContent
        const reg = /\{\{(.+?)\}\}/g
        const keyArr = [...text.matchAll(reg)]
        const keepers: TransValueKeeper[] = []
        if (keyArr.length) {
            let finnaltext = text
            keyArr.forEach((keyItem, index) => {
                let [outerkey, key] = keyItem
                let keeper: TransValueKeeper = {
                    curVal: '',
                    key,
                    outerKey: outerkey
                }
                keeper.curVal = getValue(this.vm, key)
                keepers.push(keeper)
                finnaltext = finnaltext.replace(outerkey, keeper.curVal)
                new Watcher(this.vm, key, (nval) => {
                    let context = text;
                    keepers.forEach((keeper, index2) => {
                        if (index2 === index) {
                            console.log("1111")
                            keeper.curVal = nval + ''
                        }
                        context = context.replace(keeper.outerKey, keeper.curVal)
                    })
                    node.textContent = context
                })
            })
            node.textContent = finnaltext
        }
    }

    isElementNode(node: HTMLElement) {
        return node.nodeType === 1
    }
    complieElementNode(node: HTMLElement) {
        let arrts = [...node.attributes]
        for (let attr of arrts) {
            let attr_name = attr.name;
            //处理相关指令
            if (attr_name.startsWith('v-')) {
                let name = attr_name.replace('v-', '');
                let key = attr.value
                this.updater(node, key, name)
            }else if(attr_name.startsWith('@')){
                let key = attr.value
                this.addEvent(node, key)
            }
        }
    }
    updater(node: HTMLElement, key: string, attrName: string) {
        if (attrName === 'text') {
            this.textUpdater(node, key)
        } else if (attrName === 'model') {
            this.modelUpdater(node, key)
        }
    }

    textUpdater(node: HTMLElement, key: string): void {
        node.textContent = getValue(this.vm, key)
        new Watcher(this.vm, key, (nVal) => {
            node.textContent = nVal + ''
        })
    }

    modelUpdater(node: HTMLElement, key: string): void {
        let n = node as HTMLInputElement
        if (n.value !== undefined) {
            n.value = getValue(this.vm, key)
            new Watcher(this.vm, key, (nVal) => {
                n.value = nVal + ''
            })

            n.addEventListener('input', event => {
                setValue(this.vm, key, n.value)
            })
        }else{
            throw new Error("plase check elemenet is inptElement")
        }
    }

    addEvent(node: HTMLElement, key: string) {
        let fn = getValue(this.vm, key) as Function;
        if (typeof fn !== 'function') throw new Error(`${key} is not a function;`)
        node.addEventListener('click', (e) => {
            fn.call(this.vm, [e])
        })
    }
}